// Tencent is pleased to support the open source community by making
// 蓝鲸智云 - 监控平台/日志平台 (BlueKing - Monitor/Log) available.
// Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved.
// Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://opensource.org/licenses/MIT
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//

package receiver

import (
	"io/ioutil"
	"net/http"
	"time"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/TencentBlueKing/bk-collector/define"
	"github.com/TencentBlueKing/bk-collector/internal/monitoring"
)

const (
	ContentType         = "Content-Type"
	ContentTypeProtobuf = "application/x-protobuf"
	ContentTypeJson     = "application/json"
)

type ResponseHandler interface {
	ContentType() string
	Response(rtype define.RecordType) ([]byte, error)
	WriteError(w http.ResponseWriter, err error, statusCode int)
	MarshalErrorStatus(status interface{}) ([]byte, error)
	WriteResponse(w http.ResponseWriter, msg []byte)
	Unmarshal(rtype define.RecordType, b []byte) (define.RecordData, error)
}

var fallbackMsg = []byte(`{"code": 13, "message": "failed to marshal error message"}`)

func WriteError(w http.ResponseWriter, rh ResponseHandler, err error, statusCode int) {
	s, ok := status.FromError(err)
	if !ok {
		s = status.New(codes.Unknown, err.Error())
		if statusCode == http.StatusBadRequest {
			s = status.New(codes.InvalidArgument, err.Error())
		}
	}

	b, err := rh.MarshalErrorStatus(s.Proto())
	if err != nil {
		WriteResponse(w, rh.ContentType(), http.StatusInternalServerError, fallbackMsg)
		return
	}
	WriteResponse(w, rh.ContentType(), statusCode, b)
}

func WriteResponse(w http.ResponseWriter, contentType string, statusCode int, msg []byte) {
	w.Header().Set("Content-Type", contentType)
	w.WriteHeader(statusCode)
	_, _ = w.Write(msg)
}

func HttpExport(source string, w http.ResponseWriter, rh ResponseHandler, req *http.Request, rtype define.RecordType) {
	lvs := []string{source, string(define.RequestHttp), string(rtype)}
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		monitoring.ReceiverInternalErrorTotal.WithLabelValues(lvs...).Inc()
		rh.WriteError(w, err, http.StatusInternalServerError)
		return
	}
	defer req.Body.Close()

	start := time.Now()
	data, err := rh.Unmarshal(rtype, body)
	if err != nil {
		monitoring.ReceiverDroppedTotal.WithLabelValues(lvs...).Inc()
		rh.WriteError(w, err, http.StatusBadRequest)
		return
	}

	PublishRecord(&define.Record{
		RequestType:   define.RequestHttp,
		RequestClient: define.RequestClient{IP: define.ParseRequestIP(req.RemoteAddr)},
		RecordType:    rtype,
		Data:          data.Data,
	})
	monitoring.ReceiverReceivedTotal.WithLabelValues(lvs...).Add(float64(data.Count))
	monitoring.ReceiverHandledDuration.WithLabelValues(lvs...).Observe(float64(time.Since(start).Milliseconds()))
	monitoring.ReceiverHandledTotal.WithLabelValues(lvs...).Inc()

	msg, err := rh.Response(rtype)
	if err != nil {
		monitoring.ReceiverInternalErrorTotal.WithLabelValues(lvs...).Inc()
		rh.WriteError(w, err, http.StatusInternalServerError)
		return
	}
	rh.WriteResponse(w, msg)
}
